#ifndef TCP_SERVER_H
#define TCP_SERVER_H

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <poll.h>

#include <string>

#include <singleton.h>

#include <remote_log.h>
#include <local_log.h>

class UnableToCreateSocket:public std::exception{
	public:
		UnableToCreateSocket()throw(){}
		~UnableToCreateSocket()throw(){}
		const char* what()throw(){
			return "Unable to create socket";
		}
};
class UnableToSetSocketOptions:public std::exception{
	public:
		UnableToSetSocketOptions()throw(){}
		~UnableToSetSocketOptions()throw(){}
		const char* what()throw(){
			return "Unable to set socket options";
		}
};
class UnableToBindSocket:public std::exception{
	public:
		UnableToBindSocket()throw(){}
		~UnableToBindSocket()throw(){}
		const char* what()throw(){
			return "Unable to bind socket";
		}
};
class UnableToListenSocket:public std::exception{
	public:
		UnableToListenSocket()throw(){}
		~UnableToListenSocket()throw(){}
		const char* what()throw(){
			return "Unable to listen on socket";
		}
};
class UnableToPeekSocket:public std::exception{
	public:
		UnableToPeekSocket()throw(){}
		~UnableToPeekSocket()throw(){}
		const char* what()throw(){
			return "Unable to peek in socket";
		}
};
class UnableToRecvSocket:public std::exception{
	public:
		UnableToRecvSocket()throw(){}
		~UnableToRecvSocket()throw(){}
		const char* what()throw(){
			return "Unable to receive from socket";
		}
};
class TcpServer{
	public:
		class Connection;
		TcpServer(short port, 
			sigc::slot<void, Connection> connection_handler, int queue_length=10):
			connection_handler(connection_handler),
			open(false){
			struct sockaddr_in my_addr;
			int yes=1;
		
			if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
				Singleton<RemoteLog>::Instance().send("TcpServer::TcpServer()-unable to create socket");
				Singleton<LocalLog>::Instance().send("TcpServer::TcpServer()-unable to create socket");
				throw UnableToCreateSocket();
			}
		
			if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
				Singleton<RemoteLog>::Instance().send("TcpServer::TcpServer()-unable to set socket options");
				Singleton<LocalLog>::Instance().send("TcpServer::TcpServer()-unable to set socket options");
				throw UnableToSetSocketOptions();
			}
			
			my_addr.sin_family = AF_INET;         // host byte order
			my_addr.sin_port = htons(port);     // short, network byte order
			my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
			memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct
		
			if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
				Singleton<RemoteLog>::Instance().send("TcpServer::TcpServer()-unable to bind socket");
				Singleton<LocalLog>::Instance().send("TcpServer::TcpServer()-unable to bind socket");
				throw UnableToBindSocket();
			}
		
			if (listen(sockfd, queue_length) == -1) {
				Singleton<RemoteLog>::Instance().send("TcpServer::TcpServer()-listen error");
				Singleton<LocalLog>::Instance().send("TcpServer::TcpServer()-listen error");
				throw UnableToListenSocket();
			}
			open=true;
		}
		~TcpServer(){
			if(is_open()){
				close(sockfd);
			}
		}
		bool is_open()const{
			return open;
		}
		void accept(){
			socklen_t sin_size;
			int new_fd;
			struct sockaddr_in their_addr;
			sin_size = sizeof(struct sockaddr_in);
			if ((new_fd = ::accept(sockfd, (struct sockaddr *)&their_addr,
														   &sin_size)) == -1) {
				//g_debug("accept");
				return;
			}
			try{
				Singleton<RemoteLog>::Instance().send(std::string("server: got connection from: ")+inet_ntoa(their_addr.sin_addr));
				Singleton<LocalLog>::Instance().send(std::string("server: got connection from: ")+inet_ntoa(their_addr.sin_addr));
			}
			catch(UnableToConnect& x){
				g_warning("TcpServer::accept():%s", x.what());
			}
			g_debug("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr));
			Glib::Thread::create(sigc::bind(connection_handler, Connection(new_fd, inet_ntoa(their_addr.sin_addr))), false);
		}
		class Connection{
			public:
				Connection(int new_fd, const std::string& address, bool timeoutable=true, int timeout=30000):
					new_fd(new_fd),
					address(address),
					timeoutable(timeoutable),
					timeout(timeout){
					bool blocking=true;
					if(!blocking){
						int flags=fcntl(new_fd, F_GETFL);
						flags+=O_NONBLOCK;
						fcntl(new_fd, F_SETFL, flags);
					}
				}
				void send(const std::string& s){
					if(::send(new_fd, s.c_str(), s.length(), 0)!=(int)s.length()){
						throw UnableToSend(s);
					}
				}
				~Connection(){
					//::close(new_fd);
				}
				std::string recv(){
					std::string result;
					//create pollfd struct: events=can Normal data may be read without blocking.
					//http://www.opengroup.org/onlinepubs/000095399/basedefs/poll.h.html
					pollfd pfd;
					pfd.fd=new_fd;
					pfd.events=POLLIN;
					
					//check for timeout if applicable, return early if timeout occurs
					//http://www.opengroup.org/onlinepubs/000095399/functions/poll.html
					if(timeoutable  && ::poll(&pfd,1,timeout)<=0){
						return result;
					}
					int recv_size;
					int block_size=64;
					do{
						char buf[block_size];
						if ((recv_size=::recv(new_fd, buf, block_size-1, MSG_PEEK)) == -1) {
							try{
								Singleton<RemoteLog>::Instance().send("TcpServer::Connection::recv()-receive peek error");
								Singleton<LocalLog>::Instance().send("TcpServer::Connection::recv()-receive peek error");
								throw UnableToPeekSocket();
							}
							catch(UnableToConnect& x){
								g_warning("TcpServer::accept():%s", x.what());
							}
						}
						buf[recv_size]='\0';
						std::string block(buf);
						int msg_size=block.length();
						if((recv_size=::recv(new_fd, buf, msg_size, 0)) == -1) {
							try{
								Singleton<RemoteLog>::Instance().send("TcpServer::Connection::recv()-receive error");
								Singleton<LocalLog>::Instance().send("TcpServer::Connection::recv()-receive error");
								throw UnableToRecvSocket();
							}
							catch(UnableToConnect& x){
								g_warning("TcpServer::accept():%s", x.what());
							}
						}
						result+=block;
					}
					while(recv_size==block_size-1);
					return result;
				}
				void close(){
					::close(new_fd);
				}
				std::string getAddress()const{
					return address;
				}
			private:
				//Connection(const Connection&){}
				const Connection& operator=(const Connection&){return *this;}
				int new_fd;
				std::string address;
				const bool timeoutable;
				int timeout;
		};
	private:
		int sockfd;
		//socklen_t sin_size;
		sigc::slot<void, TcpServer::Connection> connection_handler;
		bool open;
};
TcpServer::Connection& operator<<(TcpServer::Connection& conn, const std::string& s){
	conn.send(s);
	return conn;
}
TcpServer::Connection& operator>>(TcpServer::Connection& conn,  std::string& s){
	s=conn.recv();
	return conn;
}

#endif
